package ar.com.waabox.tictactoe;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.ArrayUtils;

/**
 * Basic TicTacToeGame game.
 * 
 * TODO doc.me xD
 * 
 * @author waabox (waabox [at] gmail dot com)
 * 
 * @Date Jun 22, 2009
 */
public class TicTacToeGame {
  /**
   * Players. 
   */
  public enum Player {
    PlayerOne, PlayerTwo, TIE
  };
  /**
   * State of the player who is playing. 
   */
  private Player playing;
  /**
   * Indicate the player who wons. 
   */
  private Player wonPlayer;
  /**
   * <code>
   * Here is ower board game.
   * 
   * 1  |  2   | 4  
   * 8  |  16  | 32 
   * 64 |  128 | 256
   * 
   * The idea basically is to have all possible game's hand into a number.
   * 
   *  1  + 2   + 4      = 7
   *  8  + 16  + 32     = 56
   *  1  + 8   + 64     = 73
   *  4  + 16  + 64     = 84
   *  2  + 16  + 128    = 146
   *  1  + 16  + 256    = 273
   *  4  + 32  + 256    = 292
   *  64 + 128 + 256    = 448
   * </code>
   */
  private static Integer[] possible = { 7, 56, 73, 84, 146, 273, 292, 448 };
  /**
   * Player's hands. 
   */
  private final Map<Integer, List<Player>> board = 
    new HashMap<Integer, List<Player>>();
  /**
   * box value container. 
   */
  private static final Integer[][] boxValue = new Integer[3][3];
  // init the values of the board.
  static {
    int value = 1;
    for(int x=0; x<3; x++) {
      for(int y=0; y<3; y++) {
        boxValue[x][y] = value;
        value *= 2;
      }
    }
  }
  /**
   * Default construcor.
   */
  public TicTacToeGame(Player start) {
    init();
    playing = start;
  }
  /**
   * Init the match.
   */
  private void init() {
    board.put(0, new ArrayList<Player>());
    board.put(1, new ArrayList<Player>());
    board.put(2, new ArrayList<Player>());
  }
  /**
   * Make a game play.
   * 
   * @param player {@link Player} playe who plays.
   * @param boardBox {@link BoardBox} actual played.
   */
  public void play(Player player, BoardBox boardBox) {
    if (playing == player) {
      try {
        board.get(boardBox.getX()).get(boardBox.getY());
      } catch (IndexOutOfBoundsException e) { //yeah! i do not like ifs!! xD
        board.get(boardBox.getX()).add(player);
      }
      finishHand();
    } else {
      throw new RuntimeException("It's not your turn!");
    }
  }
  /**
   * Switch between the players.
   */
  private void changePlayer() {
    playing = (Player.PlayerOne == playing) ? Player.PlayerTwo
        : Player.PlayerOne;
  }
  /**
   * Check if the game is over.
   * 
   */
  private void finishHand() {
    if(!isGameOver()) {
      int playCount = 1;
      List<Integer> totals = new ArrayList<Integer>(10);
      for (int i = 0; i < 3; i++) {
        List<Player> player = board.get(i);
        int pos = 0;
        for (Player line : player) {
          if (line == playing) {
            totals.add(boxValue[i][pos]);
          }
          pos++;
          playCount++;
        }
      }
      if(totals.size() >= 3) {
        int sum = 0;
        for (Integer value : totals) {
          sum += value;
          if(ArrayUtils.contains(possible, sum)) {
            wonPlayer = playing;
            break;
          }
        }
      }
      if(playCount == 9 && wonPlayer == null) {
        wonPlayer = Player.TIE;
      }
    }
    changePlayer();
  }
  /**
   * @return true if the game is over, false if not.
   */
  public boolean isGameOver() {
    return wonPlayer != null;
  }
  /**
   * @return {@link Player} or null if nobody wons.
   */
  public Player getWonPlayer() {
    return wonPlayer;
  }
}
